#include <lcthw/list.h>
#include <lcthw/dbg.h>

List *List_create()
{
    return calloc(1, sizeof(List));
}

void List_destroy(List *list)           
{
    LIST_FOREACH(list, first, next, cur)    // Loop through the list
    {
        if(cur->prev)                       // If there is a previous node
        {
            free(cur->prev);                // Free the previous node
        }
    }

    free(list->last);                       // Free the last node
    free(list);                             // Free the list
}

void List_clear(List *list)
{
    LIST_FOREACH(list, first, next, cur) 
    {
        free(cur->value);                   // Free the value of the current node
    }
}

void List_clear_destroy(List *list)
{
    List_clear(list);                       // Clear the list
    List_destroy(list);                     // Destroy the list
}

void List_push(List *list, void *value)
{
    ListNode *node = calloc(1, sizeof(ListNode));    // Create a new node
    check_mem(node);                                // Check if the node was created

    node->value = value;                            // Set the value of the node

    if(list->last == NULL)                          // If the list is empty
    {
        list->first = node;                         // Set the first node to the new node
        list->last = node;                          // Set the last node to the new node
    }
    else
    {
        list->last->next = node;                    // Set the next node of the last node to the new node
        node->prev = list->last;                    // Set the previous node of the new node to the last node
        list->last = node;                          // Set the last node to the new node
    }

    list->count++;                                  // Increment the count of the list  

error:
    return;
}

void *List_pop(List *list)
{
    ListNode *node = list->last;                    // Get the last node
    return node != NULL ? List_remove(list, node) : NULL; // Remove the last node
}

void List_unshift(List *list, void *value)
{
    ListNode *node = calloc(1, sizeof(ListNode));    // Create a new node
    check_mem(node);                                // Check if the node was created

    node->value = value;                            // Set the value of the node

    if(list->first == NULL)                         // If the list is empty
    {
        list->first = node;                         // Set the first node to the new node
        list->last = node;                          // Set the last node to the new node
    }
    else
    {
        node->next = list->first;                   // Set the next node of the new node to the first node
        list->first->prev = node;                   // Set the previous node of the first node to the new node
        list->first = node;                         // Set the first node to the new node
    }

    list->count++;                                  // Increment the count of the list

error:
    return;
}

void *List_shift(List *list)
{
    ListNode *node = list->first;                   // Get the first node
    return node != NULL ? List_remove(list, node) : NULL; // Remove the first node
}

void *List_remove(List *list, ListNode *node)
{
    void *result = NULL;                            // Initialize the result

    check(list->first && list->last, "List is empty."); // Check if the list is empty
    check(node, "node can't be NULL");              // Check if the node is NULL

    if(node == list->first && node == list->last)   // If the node is the only node in the list
    {
        list->first = NULL;                         // Set the first node to NULL
        list->last = NULL;                          // Set the last node to NULL
    }
    else if(node == list->first)                    // If the node is the first node
    {
        list->first = node->next;                   // Set the first node to the next node
        check(list->first != NULL, "Invalid list, somehow got a first that is NULL."); // Check if the first node is NULL
        list->first->prev = NULL;                   // Set the previous node of the first node to NULL
    }
    else if(node == list->last)                     // If the node is the last node
    {
        list->last = node->prev;                    // Set the last node to the previous node
        check(list->last != NULL, "Invalid list, somehow got a next that is NULL."); // Check if the last node is NULL
        list->last->next = NULL;                    // Set the next node of the last node to NULL
    }
    else                                            // If the node is in the middle of the list
    {
        ListNode *after = node->next;               // Get the next node
        ListNode *before = node->prev;              // Get the previous node

        after->prev = before;                       // Set the previous node of the next node to the previous node
        before->next = after;                       // Set the next node of the previous node to the next node
    }

    list->count--;                                  // Decrement the count of the list
    result = node->value;                           // Set the result to the value of the node
    free(node);                                     // Free the node

error:
    return result;
}

